package com.hyt.it.ogt.kq.service.bm.den;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.hyt.common.utils.exception.BusinessException;
import com.hyt.common.utils.result.Result;
import com.hyt.core.constant.CommonConstant;
import com.hyt.it.ogt.kq.common.bm.enums.CertificateTypeEnum;
import com.hyt.it.ogt.kq.common.bm.utils.DateUtil;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.utils.KqStringUtils;
import com.hyt.it.ogt.kq.service.bm.feign.tps.TpsClient;
import com.hyt.it.ogt.kq.service.bm.feign.tps.model.Bucket;
import com.obs.services.ObsClient;
import com.obs.services.ObsConfiguration;
import com.obs.services.exception.ObsException;
import com.obs.services.model.CompleteMultipartUploadResult;
import com.obs.services.model.PutObjectResult;
import com.obs.services.model.UploadFileRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.hyt.it.ogt.kq.service.bm.den.ExcelExport.DEFAULT_TABLE_CELL_WIDTH;

/**
 * @Description: 文件上传华为云
 * @Author: STARF
 * @CreateDate: 2022/05/30 17:36
 **/
@Slf4j
@Component
public class UploadFileToHuaweiUtil {

    @Resource
    private ConfigManager globalConfig;
    @Resource
    private TpsClient tpsClient;
    @Resource
    private ExcelExport excelExport;

    private static ObsClient obsClient;

    // MB上传转换
    private static final Long MB_SPEED_CONVERT = 1024 * 1024L;

    /**
     * xlsx方式
     *
     * @param fileName
     * @param titleColumn
     * @param titleName
     * @param titleSize
     * @param dataList
     */
    public String zipMapExcelUploadToHuaWei(String fileName, String[] titleColumn, String[] titleName, String[] typeName, int[] titleSize, List<Map<String, Object>> dataList, Boolean urlLink)
            throws Exception {
        File file = FileUtil.createTempFile(".zip", true);
        SXSSFWorkbook sSXSSFWorkbook = null;
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(file))) {
            sSXSSFWorkbook = new SXSSFWorkbook(100);
            if (titleSize == null) {
                titleSize = new int[titleColumn.length];
                for (int idx = 0; idx < titleSize.length; idx++) {
                    titleSize[idx] = DEFAULT_TABLE_CELL_WIDTH;
                }
            }
            int k = 0;
            int rowIndex;
            Sheet sheet = sSXSSFWorkbook.createSheet(fileName + (k + 1));
            // 写入excel的表头
            Row titleNameRow = sheet.createRow(0);
            for (int i = 0; i < titleName.length; i++) {
                sheet.setColumnWidth(i, titleSize[i] * 256); // 设置宽度
                Cell cell = titleNameRow.createCell(i);
                cell.setCellValue(titleName[i]);
            }
            // 写入到excel中
            if (CollUtil.isNotEmpty(dataList)) {
                if (titleColumn.length > 0) {
                    for (int index = 0; index < dataList.size(); index++) {
                        // 每个sheet3W条数据
                        if (index != 0 && (index) % 30000 == 0) {
                            k = k + 1;
                            sheet = sSXSSFWorkbook.createSheet(fileName + (k + 1));
                            // 写入excel的表头
                            titleNameRow = sSXSSFWorkbook.getSheet(fileName + (k + 1)).createRow(0);
                            for (int i = 0; i < titleName.length; i++) {
                                sheet.setColumnWidth(i, titleSize[i] * 256); // 设置宽度
                                Cell cell = titleNameRow.createCell(i);
                                cell.setCellValue(titleName[i]);
                            }
                        }
                        if (index < 30000) {
                            rowIndex = index + 1;
                        } else {
                            rowIndex = index - 30000 * ((index) / 30000) + 1;
                        }
                        Map<String, Object> studentInfo = dataList.get(index);
                        Row dataRow = sheet.createRow(rowIndex);

                        String idcard = "";
                        for (int columnIndex = 0; columnIndex < titleColumn.length; columnIndex++) {
                            String title = titleColumn[columnIndex].trim();
                            String type = typeName[columnIndex].trim();
                            if (KqStringUtils.isNotBlank(title)) {
                                Cell cell = dataRow.createCell(columnIndex);
                                String data = studentInfo.get(title) == null ? "" : studentInfo.get(title).toString();

                                switch (title) {
                                    case "createDate":
                                        String createDate = DateUtil.getStringFormatTimeStamp(data);
                                        cell.setCellValue(createDate);
                                        break;
                                    case "姓名":
                                        // 进来默认给姓名为idcard，防止没有idcard的情况导出图片名称不对
                                        idcard = data;
                                        cell.setCellValue(data);
                                        break;
                                    case "证件号码":
                                        idcard = data;
                                        cell.setCellValue(idcard);
                                        break;
                                    case "性别":
                                        if (data.equals("0")) {
                                            cell.setCellValue("男");
                                        } else if (data.equals("1")) {
                                            cell.setCellValue("女");
                                        } else {
                                            cell.setCellValue(data);
                                        }
                                        break;
                                    case "证件类型":
                                        String desc = CertificateTypeEnum.getDescByType(data);
                                        cell.setCellValue(desc);
                                        break;
                                    default:
                                        if (StrUtil.isNotBlank(data)) {
                                            if (data.equals("true")) {
                                                cell.setCellValue("是");
                                                break;
                                            } else if (data.equals("false")) {
                                                cell.setCellValue("否");
                                                break;
                                            }

                                            // 将文件类型的列进行文件保存下载
                                            if ("file".equals(type)) {
                                                if (urlLink) {
                                                    cell.setCellValue(data);
                                                } else {
                                                    data = excelExport.getFileTotalPath(data, columnIndex, idcard, zipOutputStream, cell);
                                                    cell.setCellValue(data);
                                                }
                                                break;
                                            } else if ("cloudFile".equals(type)) {
                                                if (urlLink) {
                                                    cell.setCellValue(data);
                                                } else {
                                                    data = excelExport.getCloudFileTotalPath(data, columnIndex, idcard, zipOutputStream, cell);
                                                    cell.setCellValue(data);
                                                }
                                                break;
                                            }
                                        }
                                        cell.setCellValue(data);
                                        break;
                                }
                            }
                        }
                    }
                }
            }
            ZipEntry entry = new ZipEntry("考生报名信息" + ".xls");
            zipOutputStream.putNextEntry(entry);
            sSXSSFWorkbook.write(zipOutputStream);
            zipOutputStream.flush();

            log.info("# 12、文件{}生成成功", fileName);
            // 将文件字节数组上传到华为云
            String zipUrl = this.uploadFileToHuawei(file, "zip", fileName);
            log.info("# 20、文件{}上传华为云成功", fileName);
            return zipUrl;
        } catch (Exception e) {
            log.error("# 导出Excel列表异常", e);
        } finally {
            FileUtil.del(file);
            sSXSSFWorkbook.close();
        }
        return null;
    }


    /**
     * xlsx方式
     *
     * @param fileName
     * @param dataList
     */
    @SneakyThrows
    public String zipMapExcelUploadToHuaWeiV2(String fileName, String projectId, List<List<String>> headList, List<List<String>> dataList) {
        File zipFile = FileUtil.createTempFile(".zip", true);
        try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
            // 使用 easyExcel 写到 OutputStream
            File studentListExcel = FileUtil.createTempFile("考生报考信息明细.xlsx", true);
            FileOutputStream fileOutputStream = new FileOutputStream(studentListExcel);
            EasyExcel.write(fileOutputStream).head(headList).sheet("sheet1").doWrite(dataList);
            // 使用 easyExcel 写到 OutputStream
            InputStream studentListIn = FileUtil.getInputStream(studentListExcel);

            ZipEntry entry = new ZipEntry("考生报考信息明细" + ".xls");
            zipOutputStream.putNextEntry(entry);
            IOUtils.copy(studentListIn, zipOutputStream);

            zipOutputStream.flush();
        }

        log.info("# 12、文件{}生成成功", fileName);
        // 将文件字节数组上传到华为云
        String zipUrl = this.uploadFileToHuawei(zipFile, "zip", fileName);
        log.info("# 20、文件{}上传华为云成功：{}", fileName, zipUrl);
        return zipUrl;
    }


    /**
     * 将文件字节数组上传到华为云
     *
     * @param file     文件
     * @param fileType 文件类型
     * @param fileName 文件名称
     * @return
     */
    public String uploadFileToHuawei(File file, String fileType, String fileName) {
        String fileUrl = null;
        // 获取华为云上传路径
        String hwCloudFileDict = globalConfig.getHwCloudFileDict();
        String path = hwCloudFileDict + fileType + "/" + fileName;
        // 文件上传华为云
        log.info("# 13、{}文件{}开始上传华为云", fileType, fileName);
        Result result = uploadFileToHuawei("", path, file);
        log.info("# 19、{}文件{}上传华为云：{}", fileType, fileName, JSON.toJSONString(result));
        if (result.getSuccess()) {
            fileUrl = result.getObj().toString().replace("%2F", "/");
        }
        return fileUrl;
    }

    public Result uploadFileToHuawei(String officeId, String path, byte[] bytes) {
        Result result = new Result();
        try {
            result.setSuccess(true);
            result.setObj(uploadFile(path, bytes));
        } catch (Exception e) {
            log.error("# 华为云上传文件调用失败：{}", path, e);
            result.setResult(false, "华为云上传文件调用失败");
        }
        return result;
    }

    public Result uploadFileToHuawei(String officeId, String path, File file) {
        Result result = new Result();
        try {
            result.setSuccess(true);
            result.setObj(uploadFile(path, file));
        } catch (Exception e) {
            log.error("# 18、华为云上传文件调用失败：{}", path, e);
            result.setResult(false, "华为云上传文件调用失败");
        }
        return result;
    }

    private Object uploadFile(String path, byte[] bytes) {
        Bucket bucket = this.getBucketConfig();
        PutObjectResult putObjectResult = this.getObsClient().putObject(bucket.getBucket(), path, new ByteArrayInputStream(bytes), null);
        int statusCode = putObjectResult.getStatusCode();
        if (statusCode != HttpServletResponse.SC_OK) {
            throw new RuntimeException("华为云OBS文件上传异常");
        }
        log.info("# 华为云存储上传文件返回路径：{}", putObjectResult.getObjectUrl());
        return putObjectResult.getObjectUrl();
    }

    /**
     * 断点续传文件上传
     *
     * @param path
     * @param file
     */
    private String uploadFile(String path, File file) {
        Bucket bucket = this.getBucketConfig();
        UploadFileRequest request = new UploadFileRequest(bucket.getBucket(), path);
        // 设置待上传的本地文件，localfile为待上传的本地文件路径，需要指定到具体的文件名
        request.setUploadFile(file.getPath());
        // 设置分段上传时的最大并发数
        request.setTaskNum(globalConfig.getTaskNum());
        // 设置分段大小为100MB
        request.setPartSize(globalConfig.getPartSize() * MB_SPEED_CONVERT);
        // 开启断点续传模式
        request.setEnableCheckpoint(true);
        // 设置数据传输监听器，用于获取上传进度
        request.setProgressListener(status -> {
            try {
                // 获取上传平均速率
                log.info("{}文件上传平均速率:{}MB/S", path, BigDecimal.valueOf(status.getAverageSpeed() / MB_SPEED_CONVERT).setScale(CommonConstant.TWO, RoundingMode.HALF_DOWN));
                // 获取上传进度百分比
                log.info("{}文件上传进度百分比:{}%", path, status.getTransferPercentage());
            } catch (Exception e) {

            }
        });
        // 数据反馈上传进度
        request.setProgressInterval(globalConfig.getProgressInterval() * MB_SPEED_CONVERT);
        log.info("# 14、{}断点续传请求信息：{}", path, request);
        // 进行断点续传上传
        return uploadFile(request);
    }

    /**
     * 文件断点续传到华为云
     * 在这里会进行失败重试
     *
     * @param request
     * @return
     */
    private String uploadFile(UploadFileRequest request) {
        // 是否异常
        boolean hasError;
        // 文件上传异常次数
        Integer exceptionNum = 0;
        // 文件上传成功url
        String fileUrl = null;
        // 上传开始时间
        DateTime startDateTime = new DateTime();

        ObsClient obsClient = this.getObsClient();
        do {
            hasError = Boolean.FALSE;
            try {
                // 进行断点续传上传
                CompleteMultipartUploadResult result = obsClient.uploadFile(request);
                log.info("# 15、断点续传响应信息：{}", result);
                fileUrl = result.getObjectUrl();
            } catch (ObsException e) {
                // 发生异常时可再次调用断点续传上传接口进行重新上传
                hasError = Boolean.TRUE;
                exceptionNum++;
                // 是否大于重试次数
                if (exceptionNum > globalConfig.getRetryNum()) {
                    throw new BusinessException("文件{}上传异常，失败重试次数已超最大值", request.getObjectKey());
                }
                log.error("# 16、{}文件上传异常，失败重试第{}次", request.getObjectKey(), exceptionNum);
            }
        } while (hasError);
        // 上传结束时间
        DateTime endDateTime = new DateTime();
        log.info("# 17、文件上传统计：{}文件上传开始时间{}，上传结束时间{}，上传失败{}次，总耗时{}", request.getObjectKey(), startDateTime, endDateTime, exceptionNum, DateUtil.formatBetween(startDateTime, endDateTime));
        return fileUrl;
    }

    public Bucket getBucketConfig() {
        Bucket bucket = null;
        try {
            String json = tpsClient.getBucketTempCredential();
            if (!KqStringUtils.isEmpty(json)) {
                bucket = JSON.parseObject(json, Bucket.class);
            }
        } catch (Exception e) {
            log.error("获取Bucket配置异常", e);
        }
        return bucket;
    }

    /**
     * 使用永久凭证，单例模式
     *
     * @return
     */
    public synchronized ObsClient getObsClient() {
        if (obsClient == null) {
            Bucket bucket = getBucketConfig();
            ObsConfiguration config = new ObsConfiguration();
            config.setSocketTimeout(30000);
            config.setConnectionTimeout(10000);
            config.setEndPoint(bucket.getEndPoint());
            // 查找默认配置
            synchronized (this) {
                if (obsClient == null) {
                    obsClient = new ObsClient(bucket.getAccessKey(), bucket.getSecretKey(), config);
                }
            }
        }
        return obsClient;
    }

}
